# coding=utf-8
from gm.api import run, subscribe, unsubscribe, schedule, MODE_LIVE, MODE_BACKTEST, ADJUST_PREV, OrderStatus_Filled, OrderStatus_Rejected, OrderSide_Buy, OrderSide_Sell, PositionSide_Long
from quant.vlogger import VLogger
from quant import strategy
from quant.trader import juejin
from quant.data import jisilu, utils
from datetime import datetime, time, timedelta
from easydict import EasyDict

import pandas as pd
import pathlib
import pickle
import click
from sys import platform

start_date = '2022-04-07'
end_date = str(datetime.now().date() - timedelta(days=1))


# 按照策略生成标的建仓
def fill_positions(context):
    num_positions = context.top - len(context.account().positions())
    if num_positions > 0:
        t1 = 0.98 * context.trader.nav(context) / context.top
        t2 = 0.98 * context.trader.cash(context) / num_positions
        target_value = t1 if t1 < t2 else t2
        VLogger.info('买入%s个标的, 每个目标价值: %s' %
                     (num_positions, target_value))
        bars = context.data_fn(context, None)
        signals = context.strategy.generate_signal(bars,
                                                   context.now).reset_index()
        signals = signals.query('order_book_id not in %s' % [
                                p.symbol for p in context.account().positions()]).head(num_positions)
        for s in signals.itertuples():
            target_volume = int(target_value / s.open)
            target_volume -= target_volume % 10
            VLogger.vlog(1, '{}: 买入标的: {}'.format(context.now,
                                                  s.order_book_id))
            context.trader.order_target_volume(
                context, s.order_book_id, target_volume, s.open)
    else:
        VLogger.vlog(1, '达到持仓数量上限%s' % context.top)


def init(context):
    VLogger.v = 1
    VLogger.info('可转债埋伏条件单')
    context.top = 20
    context.conds = {}
    context.dt = context.now.time().minute
    context.confirm = False
    cfg = strategy.ConbondStrategy.strategy_cfg(
        'multi_factors_rank', {
            'cpr_threshold': 10,  # 转股溢价率阈值
            'threshold': 1300,  # 价格阈值
            'size': 600000000,  # 规模阈值（元）
            'rebalance': '日',  # 轮动周期
            'days_to_stop_trading': 10,  # 距离停止交易日阈值
            'days_from_listing': 10,  # 距离首次上市交易日阈值
            'weight_price': 0.0,  # 价格权重
            'weight_cpr': 0.6,  # 转股溢价率权重
            'weight_remaining_size': 0.4,  # 剩余规模权重
            'top': context.top,  # 选取标的个数
        })
    data_dir = pathlib.Path(__file__).parent.parent.joinpath('data')

    if context.mode == MODE_LIVE:
        context.data_fn = lambda context, bar_dict: jisilu.refresh_now(
            data_dir.joinpath('jisilu'), to_juejin=True)[1]
        context.cache_dir = pathlib.Path(__file__).parent.joinpath('live')
        context.freq = '60s'
    else:
        context.data_fn = utils.conbond_backtest_data_fn(start_date, data_dir)
        context.cache_dir = pathlib.Path(__file__).parent.joinpath('backtest')
        context.freq = '60s'
    context.trader = juejin.JuejinTrader()
    context.strategy = strategy.ConbondRotateStrategy(
        cfg, context.data_fn, context.trader)

    # Recover conds from positions
    for p in context.account().positions():
        with open(context.cache_dir.joinpath('%s.pkl' % p.symbol),
                  'rb') as f:
            cond = pickle.load(f)
            context.conds[p.symbol] = cond
            subscribe(symbols=p.symbol, frequency='1d')
            subscribe(symbols=p.symbol, frequency=context.freq)

    schedule(schedule_func=fill_positions,
             date_rule='1d',
             time_rule='09:35:00')
    if context.mode == MODE_LIVE and context.now.time() > time(9, 35, 0):
        fill_positions(context)


def on_bar(context, bars):
    if context.dt != context.now.time().minute:
        context.dt = context.now.time().minute
        if context.mode == MODE_LIVE:
            VLogger.info('%s' % context.now)
        elif context.dt % 10 == 0:
            VLogger.info('%s' % context.now)

    for bar in bars:
        if bar.symbol not in context.conds:
            continue
        day_bar = context.data(symbol=bar.symbol, frequency='1d', count=1)
        cond = context.conds[bar.symbol]
        df = pd.DataFrame([{
            'order_book_id': bar.symbol,
            'open': bar.open,
            'day_open': day_bar.open[0],
            'datetime': context.now
        }]).set_index(['datetime', 'order_book_id'])
        signal = cond.generate_signal(df, context.now).iloc[0]
        with context.cache_dir.joinpath('%s.pkl' %
                                        bar.symbol).open(mode='wb') as f:
            pickle.dump(cond, f)
        if signal.signal == strategy.TradeOp.CLOSE:
            if context.confirm:
                context.confirm = click.confirm(
                    'Closing %s' % signal.order_book_id, default=True)
            context.trader.order_target_volume(
                context, bar.symbol, 0, signal.open)


def on_order_status(context, order):
    dt = context.now
    if order.status == OrderStatus_Filled:
        VLogger.vlog(
            0, '{}: {} order_book_id: {}, price: {}, volume: {}, value: {}'.
            format(dt, '买入' if order.side == OrderSide_Buy else '卖出',
                   order.symbol, order.price, order.volume, order.value))
        if order.side == OrderSide_Buy:
            cond = strategy.TurningPointSell(
                order.symbol, context.now, order.price, EasyDict({
                    'drop_threshold': 0.02,  # 回落卖出阈值
                    'daily_increase_threshold': 0.08,  # 日内拉升卖出阈值 
                    'cummulated_increase_threshold': 0.12,  # 累计涨幅卖出阈值
                    }))
            with context.cache_dir.joinpath('%s.pkl' %
                                            order.symbol).open(mode='wb') as f:
                pickle.dump(cond, f)
            context.conds[order.symbol] = cond
            subscribe(symbols=order.symbol, frequency='1d')
            subscribe(symbols=order.symbol, frequency=context.freq)
        elif order.side == OrderSide_Sell:
            cond = context.conds.pop(order.symbol)
            with context.cache_dir.joinpath('%s-%s.pkl' %
                                            (context.now.date(), order.symbol)).open(mode='wb') as f:
                pickle.dump(cond, f)
            unsubscribe(symbols=order.symbol)
        else:
            raise Exception('Fatal: Invalid order side')
    elif order.status == OrderStatus_Rejected:
        VLogger.error('{}: status: {}, target: {}, error: {}'.format(
            dt, order.status, order.symbol, order.ord_rej_reason_detail))
        if context.confirm:
            VLogger.error(order)
            VLogger.error(context.account().position(order.symbol,
                                                     PositionSide_Long))
            context.confirm = click.confirm('Error', default=True)
    else:
        VLogger.vlog(2, '{}: {}'.format(dt, order))


# 查看最终的回测结果
def on_backtest_finished(context, indicator):
    VLogger.info(indicator)


if __name__ == '__main__':
    '''
        strategy_id策略ID, 由系统生成
        filename文件名, 请与本文件名保持一致
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID, 可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        '''
    import logging
    logging.getLogger().setLevel(logging.INFO)
    run(strategy_id='f22cb648-cab3-11ec-b6c3-2a16a8610fe9',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='567120cd2f9b2ce8ae3fbd7ee76ed06bf899b512',
        backtest_start_time='%s 00:00:00' % start_date,
        backtest_end_time='%s 23:59:59' % end_date,
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001)
